當master傳送的request不符合格式、slave不支援此功能等問題時,代表slave不能正確解碼request內容,這時slave就會response一個error response,其組成為[slave ID][error code][exception code]
Modbus其中一重要的概念是暫存器,不同地址的暫存器存放著不同數據類型型與讀寫特性得資料,而這裡所說的暫存器地址不一定是固定的內存地址,也可以是開發者自行定義的連續或不連續的一塊儲存區域。我們可以把Modbus暫存器分為如圖2-1所示的4大主要部分。
[從機地址] [功能碼] [暫存器地址] [暫存器數量(word數)] [驗證碼]
ex : [1F] [04] [00 0A] [00 04] [D2 75]
[從機地址] [功能碼] [幾個bytes] [data] [驗證碼]
ex : [1F] [04] [08] [00 01 FF FF 00 00 00 00] [54 FE]
Modbus封包之間的區隔是使用3.5個字符時間來判斷,在該時間區間內,master與slave不做任何動作,利用字符時間的機制只適用於Modbus RTU。
封包之間的時間間格在計算上其實還取決於鮑率,還有格式問題:
依據Modbus國家規範,一個字符時間為處理11 bits資料所需時間,一般包含一個起始位、8個數據位、1個校驗位 、一個停止位,而不是所謂一個byte(8 bits)的時間,這裡不要搞混,這是官方規定
所謂封包之間的間隔指的是在serial port上的所有封包,包含request、reaponse,只要有資料流淌,就必須規定他們之間的區分方法
所以我們假設當前鮑率為9600,也就是9600 bits per second,而3.5個字符時間就等於3.5*11=38.5位,那麼我們可以求得該Modbus RTU要求封包之間的時間間格為 : (38.5*1000)/9600 = 4.0104167ms
由於頻繁的計算會對CPU造成負擔,所以Modbus官方規定,當鮑率超過19200時,封包時間間格會固定為1.75ms
另外一種較為簡易的設定是不管鮑率為何,統一設定成10ms的時間間格,好處是釋放cpu壓力、較為簡便,對於對時間要求沒有那麼即時的系統,壞處是效率較低、不夠精準
另外Modbus還有一個字元之間的判斷,一樣是利用字符時間機制,假設一個數據中相鄰兩個字元的時間差在1.5個
字符時間以上,就會判斷該筆資料丟失 (1.5*11*1000)/baud rate ms,當鮑率超過19200時,時間間格設定成定值0.75ms
Modbus的ID號為1bytes(0~255),但是前面有提過,slave最多只能設定成247,這是因為0是廣播模式,而剩餘的248~255是Modbus的保留ID,供更高級開發用,所以實際應用上我們能設定的範圍在1~247之間。
Modbus的功能碼主要對應四個大方向—DO, DI, AO, AI,其中的功能碼主要也是處理這四大方向(對應連接的slave提供的可能是溫度數據、濕度數據、是否上鎖等數位類比消息)。功能碼占總封包1個byte,不過允許範圍在1~127,0在上面有提到分配給廣播模式,而128~255之所以不分配主要原因為假如1~127發生錯誤加上0x80的錯誤碼後會變成讓上限變成255,所以若是開放128以後的數會讓功能碼區段超過1個bytes的範圍。
Request
請求部份我們首先填入功能碼01,再來需要注意的是起始地址為2個bytes,這裡填的Modbus的地址主要由使用者決定(0x0000~0xffff)分別為HI高位與LO低位,由線圈20~38(Dec)可得需請求19個線圈數據,但是我們實際讀取的是Modbus地址19~37(Dec)因此將首地址轉換成HEX為0x0013,數量地址也分成高低兩位共2個bytes,所以我們可以得到00Hi 13Lo。
請求封包佔8bytes長度。
Response
如果我們要讀取線圈20~38的狀態,需要先計算總共需要回傳38-20+1=19個bits,19/8=2...3,不能整除所以要多分配一個bytes(用來補足的bits都設定成0),可以得出回傳的data佔有3個bytes。假設線圈20~27的狀態分別為on-on-off-off-on-on-off-on,要注意我們需要從LSB開始存取狀態,因此Binary Status為 1100 1101轉換成HEX就是CD,以此類推我們可以得到圖2-7的資料封包的排序如下所示。
響應封包的資料長度視請求的暫存器數量而定,最長不超過256bytes,data最長2000/8=250bytes。
Request
請求部份我們首先填入功能碼02,再來需要注意的是起始地址為2個bytes,這裡填的Modbus的地址主要由使用者決定(0x0000~0xffff)分別為HI高位與LO低位,由線圈19~218(Dec)可得需請求22個線圈數據,但是我們實際讀取的是Modbus地址196~217(Dec)因此將首地址轉換成HEX為0x00C4,數量地址也分成高低兩位共2個bytes,所以我們可以得到00Hi C4Lo。
Response
如果我們要讀取線圈197~218的狀態,需要先計算總共需要回傳218-197+1=22個bits,22/8=2...6,不能整除所以要多分配一個bytes(用來補足的bits都設定成0),可以得出回傳的data佔有3個bytes。假設線圈197~204的狀態分別為off-off-on-on-off-on-off-on,要注意我們需要從LSB開始存取狀態,因此Binary Status為 1010 1100轉換成HEX就是CD,以此類推我們可以得到圖2-8的資料封包的排序如下所示。
Request
請求部份我們首先填入功能碼03,再來需要注意的是起始地址為2個bytes,這裡填的Modbus的地址主要由使用者決定(0x0000~0xffff)分別為HI高位與LO低位,由暫存器地址108~110(Dec)可得需請求3個暫存器數據,但是我們實際讀取的是Modbus地址107~109(Dec)因此將首地址轉換成HEX為0x006B,數量地址也分成高低兩位共2個bytes,所以我們可以得到00Hi 6BLo。
Response
如果我們要讀取暫存器108~110的狀態,需要先計算總共需要回傳110-108+1=3個Word,2*3=6個bytes,可以得出回傳的data佔有6個bytes。假設暫存器108的輸出為555(Dec),我們可以經過轉換得到0x022B,然後依高低位分別填入02Hi 2BLo,依此類推暫存器109、110輸出分別為0以及100,經過轉換分別得到00 00與00 64(HEX)。
另外假如回傳暫存器狀態設定成32bits,那麼暫存器取值就需要佔用到108、109兩個暫存器位置共4bytes,下一個暫存器將從110開始。
Request
請求部份我們首先填入功能碼04,再來需要注意的是起始地址為2個bytes,這裡填的Modbus的地址主要由使用者決定(0x0000~0xffff)分別為HI高位與LO低位,範例中我們要讀取暫存器9的數值,但是我們實際讀取的是Modbus地址08因此將首地址轉換成HEX為0x0008,數量地址也分成高低兩位共2個bytes,所以我們可以得到00Hi 08Lo。
Response
如果我們要讀取暫存器9的狀態,總計1個Word,共2個bytes,可以得出回傳的data佔有2個bytes。假設暫存器9的輸出為10(Dec),我們可以經過轉換得到0x000A,然後依高低位分別填入00Hi 0ALo。
另外假如回傳暫存器狀態設定成32bits,那麼暫存器取值就需要佔用4bytes,那麼取直就需要暫用暫存器9與10的位置。